package com.astruyk.chordophone;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.util.AttributeSet;
import android.view.View;

/**
 * A view to display a chord that is intended to be more compact than the standard display. It
 * should be rendered such that multiple versions of it can be present comfortably on the same
 * screen.
 * 
 * @author Anton Struyk
 *
 */
public class CompactChordView extends View {
	/**
	 * The least number of frets to display at the same time. Even if
	 * a chord could be displayed in fewer frets, it will draw this many.
	 */
	private final int MINIMUM_FRET_COUNT = 3;
	
	/**
	 * The space to use between strings.
	 */
	private final int STRING_SPACING = 10;
	
	/**
	 * The spacing to use between frets.
	 */
	private final int FRET_SPACING = 20;
	
	/**
	 * The radius of the circle to draw that represents
	 * the fingering position for this chord.
	 */
	private final int FINGERING_MARKER_RADIUS = 5;
	
	/**
	 * The maximum width of the fret number label
	 */
	private final int LABEL_WIDTH = 8;
	
	/**
	 * The number of strings to display before there is
	 * a chord assigned.
	 */
	private final int DEFAULT_STRINGS = 4;
	
	/**
	 * An amount of padding to add to the top and bottom when drawing fret bars
	 */
	private final int TOP_AND_BOTTOM_PADDING = 3;
	
	private Chord _chord;
	private Paint _fretPaint;
	private Paint _nutPaint;
	private Paint _stringPaint;
	private Paint _fingeringPaint;
	private Paint _labelPaint;
	
	public CompactChordView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initialize();
	}

	public CompactChordView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initialize();
	}

	public CompactChordView(Context context) {
		super(context);
		initialize();
	}
	
	private void initialize(){
		_fretPaint = new Paint();
		_fretPaint.setAntiAlias(true);
		_fretPaint.setColor(Color.WHITE);
		_fretPaint.setStrokeWidth(1);
		
		_nutPaint = new Paint(_fretPaint);
		_nutPaint.setStrokeWidth(3);
		_nutPaint.setStrokeCap(Cap.ROUND);

		_stringPaint = new Paint(_fretPaint);
		_stringPaint.setStrokeWidth(1);
		
		_fingeringPaint = new Paint(_fretPaint);
		_fingeringPaint.setAntiAlias(true);
		
		_labelPaint = new Paint();
		_labelPaint.setTextSize(12);
		_labelPaint.setColor(Color.WHITE);
		_labelPaint.setAntiAlias(true);
	}
	
	/**
	 * Sets the chord to be displayed in this view.
	 * @param chord the chord to be displayed, or null to draw the defaults.
	 */
	public void setChord(Chord chord){
		_chord = chord;
		invalidate();
	}
	
	@Override
	public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		setMeasuredDimension(
				measureWidth(widthMeasureSpec),
				measureHeight(heightMeasureSpec));
	}
	
	private int measureWidth(int widthMeasureSpec) {
		final int specMode = MeasureSpec.getMode(widthMeasureSpec);
		final int specSize = MeasureSpec.getSize(widthMeasureSpec);
		
		if(specMode == MeasureSpec.EXACTLY){
			return specSize;
		}
		
		final int numberOfStrings = _chord == null ? DEFAULT_STRINGS : _chord.getFingering().length;
		
		final int result =
			(STRING_SPACING * numberOfStrings)
			+ (FINGERING_MARKER_RADIUS * 2)
			+ LABEL_WIDTH;
		
		if(specMode == MeasureSpec.AT_MOST){
			return Math.min(specSize, result);
		}
		return result;
	}
	
	private int measureHeight(int heightMeasureSpec) {
		final int specMode = MeasureSpec.getMode(heightMeasureSpec);
		final int specSize = MeasureSpec.getSize(heightMeasureSpec);
		
		if(specMode == MeasureSpec.EXACTLY){
			return specSize;
		}
		int result = getFretsToDraw() * FRET_SPACING + (2 * TOP_AND_BOTTOM_PADDING);
		
		if(specMode == MeasureSpec.AT_MOST){
			return Math.min(specSize, result);
		}
		
		return result;
	}
	
	/**
	 * Figures out the number of frets that are required to
	 * be drawn for this chord.
	 * @return the minimum of frets needed to represent this chord, or the minimum allowed fret count
	 */
	private int getFretsToDraw(){
		if(_chord != null){
			
			
			//Calculate the range of held notes that are required
			int min = Integer.MAX_VALUE;
			int max = 0;
			boolean isRangeValid = false;
			for(int fret : _chord.getFingering()){
				//Don't count open or muted strings when considering frets to hold down
				if(fret == 0 || fret < 0) { continue; }
				
				min = Math.min(min, fret);
				max = Math.max(max, fret);
				isRangeValid = true;
			}
			
			if(isRangeValid)
			{
				return Math.max((max - min) + 1, MINIMUM_FRET_COUNT); 
			}
		}
		return MINIMUM_FRET_COUNT;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		
		//Cache some values
		final int[] fingering = _chord == null ? new int[DEFAULT_STRINGS] : _chord.getFingering();
		final int stringCount = fingering.length;
		final int fretsToDraw = getFretsToDraw();
		final int height = getHeight();
		
		//Draw the frets, we need to draw one more line
		//than there are visible frets (for the bottom line)
		for(int i = 0; i < (fretsToDraw + 1); i++){
			final int startX = FINGERING_MARKER_RADIUS;
			final int stopX = startX + (STRING_SPACING * (stringCount - 1));
			final int y = TOP_AND_BOTTOM_PADDING + i * FRET_SPACING;
			canvas.drawLine(startX, y, stopX, y, _fretPaint);
		}
		
		//Determine how many frets down the fretboard we need to shift to keep the chord
		//in view
		int startingFret = Integer.MAX_VALUE;
		int endingFret = 1;
		for(int currentFret : fingering){
			//Don't consider open or muted frets as being held
			if(currentFret == 0 || currentFret < 0) { continue; }
			startingFret = Math.min(currentFret, startingFret);
			endingFret = Math.max(currentFret, endingFret);
		}
		if(startingFret == Integer.MAX_VALUE || endingFret < MINIMUM_FRET_COUNT)
		{
			startingFret = 1;
		}

		for(int i = 0; i < stringCount; i++){
			int x = FINGERING_MARKER_RADIUS + (i * STRING_SPACING);
			
			//Draw the string
			canvas.drawLine(x, TOP_AND_BOTTOM_PADDING, x, height - TOP_AND_BOTTOM_PADDING, _stringPaint);
			
			//Figure out how many frets down the from starting fret this note is
			final int relativeFret = (fingering[i] - startingFret);
			
			//Draw the note on the string
			int fingeringY =
				TOP_AND_BOTTOM_PADDING
				+ (FRET_SPACING * relativeFret)
				+ FRET_SPACING / 2;
			if(fingering[i] < 0){
				//Draw an X, for muted string
				canvas.drawLine(
						x - FINGERING_MARKER_RADIUS,
						fingeringY - FINGERING_MARKER_RADIUS,
						x + FINGERING_MARKER_RADIUS,
						fingeringY + FINGERING_MARKER_RADIUS,
						_fingeringPaint);
				canvas.drawLine(
						x + FINGERING_MARKER_RADIUS,
						fingeringY - FINGERING_MARKER_RADIUS,
						x - FINGERING_MARKER_RADIUS,
						fingeringY + FINGERING_MARKER_RADIUS,
						_fingeringPaint);
			}else if(fingering[i] > 0){
				canvas.drawCircle(
						x,
						fingeringY,
						FINGERING_MARKER_RADIUS,
						_fingeringPaint);
			}
		}
		
		//Draw the nut at the top of the screen if we're drawing
		//at the top of the neck.
		if(startingFret == 1){
			canvas.drawLine(
					FINGERING_MARKER_RADIUS,
					TOP_AND_BOTTOM_PADDING,
					FINGERING_MARKER_RADIUS + ((stringCount - 1) * STRING_SPACING),
					TOP_AND_BOTTOM_PADDING,
					_nutPaint);
		}
		
		//Draw the label beside the screen frets
		for(int i = 0; i < fretsToDraw; i++){
			final String label = "" + (i + startingFret);
			final int x =
				FINGERING_MARKER_RADIUS
				+ ((stringCount - 1) * STRING_SPACING)
				+ FINGERING_MARKER_RADIUS
				+ 2;
			final int y =
				TOP_AND_BOTTOM_PADDING
				+ (i * FRET_SPACING)
				+ (FRET_SPACING / 2)
				+ 4;
			canvas.drawText(label, x, y, _labelPaint);
		}
	}
}
